package com.gulimall.product.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.common.constant.ProductConstant;
import com.common.to.Seckill.SeckillSku;
import com.common.to.Seckill.SeckillSkuRedisTo;
import com.common.to.SkuReduceTo;
import com.common.to.SpuBoundsTo;
import com.common.to.SpuInfoTo;
import com.common.to.es.SkuEsModel;
import com.common.utils.R;
import com.gulimall.product.dao.*;
import com.gulimall.product.entity.*;
import com.gulimall.product.feign.CouponFeignService;
import com.gulimall.product.feign.SearchFeignService;
import com.gulimall.product.feign.SeckillFeignService;
import com.gulimall.product.feign.WareFeignService;
import com.gulimall.product.vo.*;
import com.gulimall.product.vo.ItemVo.SkuItemSaleAttrVo;
import com.gulimall.product.vo.ItemVo.SkuItemVo;
import com.gulimall.product.vo.ItemVo.SpuItemAttrGroupVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.common.utils.PageUtils;
import com.common.utils.Query;

import com.gulimall.product.service.SpuInfoService;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Slf4j
@Service("spuInfoService")
public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {

    @Resource
    SpuInfoDao spuInfoDao;
    @Resource
    private SpuInfoDescDao spuInfoDescDao;
    @Resource
    private SpuImagesServiceImpl spuImagesServiceImpl;
    @Resource
    private AttrDao attrDao;
    @Resource
    private ProductAttrValueServiceImpl productAttrValueServiceImpl;
    @Resource
    private SkuImagesServiceImpl skuImagesServiceImpl;
    @Resource
    private SkuInfoDao skuInfoDao;
    @Resource
    private SkuSaleAttrValueServiceImpl skuSaleAttrValueServiceImpl;
    @Resource
    private CouponFeignService couponFeignService;
    @Resource
    private WareFeignService wareFeignService;
    @Resource
    private BrandDao brandDao;
    @Resource
    private CategoryDao categoryDao;
    @Resource
    private SkuSaleAttrValueDao skuSaleAttrValueDao;
    @Resource
    private SearchFeignService searchFeignService;
    @Resource
    private SkuImagesDao skuImagesDao;
    @Resource
    private ProductAttrValueDao productAttrValueDao;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private SeckillFeignService seckillFeignService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();
        // 模糊查询
        String key = (String) params.get("key");
        if(StringUtils.isNotBlank(key)){
            queryWrapper.and(query->{
                query.like("spu_name",key).or().like("spu_description",key);
            });
        }
        // 状态
        String status = (String) params.get("status");
        if(StringUtils.isNotBlank(status)){
            queryWrapper.eq("publish_status",status);
        }
        // 品牌ID
        String brandId = (String) params.get("brandId");
        if(StringUtils.isNotBlank(brandId)){
            queryWrapper.eq("brand_id",brandId);
        }
        // 分类ID
        String cateId = (String) params.get("catelogId");
        if(StringUtils.isNotBlank(cateId)){
            queryWrapper.eq("catalog_id",cateId);
        }

        IPage<SpuInfoEntity> page = this.page(
                new Query<SpuInfoEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Transactional
    @Override
    public void saveSpu(SpuSaveVo spuSaveVo) {
        // 1、保存基本信息 pms_spu_info
        SpuInfoEntity spuInfoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(spuSaveVo,spuInfoEntity);
        spuInfoEntity.setCreateTime(new Date());
        spuInfoEntity.setUpdateTime(new Date());
        spuInfoDao.insert(spuInfoEntity);
        Long spuId = spuInfoEntity.getId();

        // 2、保存spu描述图片 pms_spu_info_desc
        SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity();
        spuInfoDescEntity.setSpuId(spuId);
        spuInfoDescEntity.setDecript(StringUtils.join(spuSaveVo.getDecript(),","));
        spuInfoDescDao.insert(spuInfoDescEntity);

        // 3、保存 spu 的图片集 pms_spu_images
        ArrayList<SpuImagesEntity> spuImagesList = new ArrayList<>();
        List<String> imageList = spuSaveVo.getImages();
        // 遍历 image 数组，如果不为空，就创建对应对象，然后加入数组，最后进行批处理
        imageList.forEach(image->{
            if(StringUtils.isNotBlank(image)){
                SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
                spuImagesEntity.setSpuId(spuId);
                spuImagesEntity.setImgUrl(image);
                spuImagesList.add(spuImagesEntity);
            }
        });
        spuImagesServiceImpl.saveBatch(spuImagesList);

        // 4、保存spu的规格参数 pms_product_attr_value
        List<BaseAttrs> baseAttrsList = spuSaveVo.getBaseAttrs();
        // 获取当前属性attrId集合，然后通过 attrId 集合获取 attr对象并封装成 Map 结构<attrId, attrName>
        List<Long> attrIdList = baseAttrsList.stream().map(BaseAttrs::getAttrId).collect(Collectors.toList());
        Map<Long, String> attrIdMap = attrDao.selectBatchIds(attrIdList)
                .stream().collect(Collectors.toMap(AttrEntity::getAttrId, AttrEntity::getAttrName));
        // 遍历传参的属性信息集合，封装成对象添加到数组中，然后进行批插入
        List<ProductAttrValueEntity> productAttrValueEntityList = baseAttrsList.stream().map(baseAttrs -> {
            ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();
            productAttrValueEntity.setAttrId(baseAttrs.getAttrId());
            productAttrValueEntity.setAttrName(attrIdMap.get(baseAttrs.getAttrId()));
            productAttrValueEntity.setQuickShow(baseAttrs.getShowDesc());
            productAttrValueEntity.setSpuId(spuId);
            productAttrValueEntity.setAttrValue(baseAttrs.getAttrValues());
            return productAttrValueEntity;
        }).collect(Collectors.toList());
        productAttrValueServiceImpl.saveBatch(productAttrValueEntityList);

        // 5、保存 spu 积分信息，gulimall-sms   ->  sms_spu_bounds
        Bounds bounds = spuSaveVo.getBounds();
        SpuBoundsTo spuBoundsTo = new SpuBoundsTo();
        BeanUtils.copyProperties(bounds,spuBoundsTo);
        spuBoundsTo.setSpuId(spuId);
        R r1 = couponFeignService.saveFeign(spuBoundsTo);
        if(!r1.getCode().equals(0)){
            log.error("调用远程进行积分信息保存失败！");
        }

        // 6、 保存当前 spu 对应所有 sku 信息
        List<Skus> skusList = spuSaveVo.getSkus();
        for (Skus skus : skusList) {
            // 6.1 sku 基本信息 pms_sku_info
            // 先找出默认的图片
            List<Images> imagesList = skus.getImages();
            String defaultUrl = null;
            for (Images images : imagesList) {
                if(images.getDefaultImg() == 1){
                    defaultUrl = images.getImgUrl();
                    break;
                }
            }
            // 然后进行属性赋值添加
            SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
            BeanUtils.copyProperties(skus, skuInfoEntity);
            skuInfoEntity.setBrandId(spuSaveVo.getBrandId());
            skuInfoEntity.setCatalogId(spuSaveVo.getCatalogId());
            skuInfoEntity.setSpuId(spuId);
            skuInfoEntity.setSkuDesc(spuSaveVo.getSpuDescription());
            skuInfoEntity.setSaleCount(0L);
            skuInfoEntity.setSkuDefaultImg(defaultUrl);
            skuInfoDao.insert(skuInfoEntity);

            // 6.2 sku 图片信息 pms_sku_images
            List<SkuImagesEntity> skuImagesEntityList = new ArrayList<>();
            imagesList.forEach(images -> {
                if(StringUtils.isNotBlank(images.getImgUrl())){
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    BeanUtils.copyProperties(images, skuImagesEntity);
                    skuImagesEntity.setSkuId(skuInfoEntity.getSkuId());
                    skuImagesEntityList.add(skuImagesEntity);
                }
            });
            skuImagesServiceImpl.saveBatch(skuImagesEntityList);

            // 6.3 sku的销售属性信息 pms_sku_sale_attr_value
            List<Attr> attrList = skus.getAttr();
            List<SkuSaleAttrValueEntity> skuSaleAttrValueEntityList = attrList.stream().map(attr -> {
                SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity();
                BeanUtils.copyProperties(attr, skuSaleAttrValueEntity);
                skuSaleAttrValueEntity.setSkuId(skuInfoEntity.getSkuId());
                return skuSaleAttrValueEntity;
            }).collect(Collectors.toList());
            skuSaleAttrValueServiceImpl.saveBatch(skuSaleAttrValueEntityList);

            // 6.4 sku 的优惠、满减信息  gulimall-sms   sms_sku_ladder、sms_sku_full_reduction、sms_member_price
            SkuReduceTo skuReduceTo = new SkuReduceTo();
            BeanUtils.copyProperties(skus, skuReduceTo);
            skuReduceTo.setSkuId(skuInfoEntity.getSkuId());
            R r = couponFeignService.saveFeign(skuReduceTo);
            if(!r.getCode().equals(0)){
                log.error("调用远程进行优惠信息保存失败！");
            }
        }

    }

    /**
     * 对指定的spu所有商品进行上架操作
     * @param spuId
     */
    @Override
    public boolean up(Long spuId) {
        // 1、准备数据
        // 1）计算展示属性数据集合
            // 1))查出商品的所有 sku 信息数据集合
        List<SkuInfoEntity> skuInfoEntityList = skuInfoDao.selectList(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
            // 2))获取当前 spu 下所有的 skuID
        List<Long> skuIdList = skuInfoEntityList.stream().map(SkuInfoEntity::getSkuId).collect(Collectors.toList());
            // 3))获取spuId相关的属性值集合
        List<SkuSaleAttrValueEntity> skuSaleAttrValueEntityList = skuSaleAttrValueDao.selectListBySkuIdList(skuIdList);
            // 4)）计算当前spu 关联的属性ID集合
        Set<Long> attrIdSet = skuSaleAttrValueEntityList.stream().map(SkuSaleAttrValueEntity::getAttrId).collect(Collectors.toSet());
            // 5)）根据 attrId 集合来筛选要展示的属性ID集合
        List<Long> searchIdList = attrDao.selectSearchIdByIdSet(attrIdSet);
            // 6)) 根据3))查出的数据以及5))查出的展示属性ID集合来得出最终的Map结构数据
        List<SkuSaleAttrValueEntity> searchAttrValueList = skuSaleAttrValueEntityList.stream()
                // 过滤不包含的
                .filter(skuSaleAttrValueEntity -> searchIdList.contains(skuSaleAttrValueEntity.getAttrId()))
                // 封装成数据
                .collect(Collectors.toList());

        // 2）调用远程 ware 服务，获取库存Map结构数据，<skuId,stock>
        R r = null;
        Map<Long,Integer> stockMap = null;
        try {
            r = wareFeignService.getStockMap(skuIdList);
            TypeReference<Map<Long, Integer>> typeReference = new TypeReference<Map<Long, Integer>>() {};
            stockMap = r.getData(typeReference);
        } catch (Exception e) {
            log.info("调用 ware 查询库存出错"+e);
            return false;
        }

        // 2、遍历集合，封装要存入 ES 的数据
        Map<Long, Integer> finalStockMap = stockMap;
        List<SkuEsModel> skuEsModelList = skuInfoEntityList.stream().map(skuInfoEntity -> {
            SkuEsModel skuEsModel = new SkuEsModel();
            BeanUtils.copyProperties(skuInfoEntity, skuEsModel);
            // 未匹配的属性：skuPrice、skuImg、hasStock、hotScore、brandName、brandImg、catalogName、attrs
            // 1）设置 skuPrice、skuImg
            skuEsModel.setSkuPrice(skuInfoEntity.getPrice());
            skuEsModel.setSkuImg(skuInfoEntity.getSkuDefaultImg());
            // 2）设置 branName、brandId
            BrandEntity brandEntity = brandDao.selectById(skuInfoEntity.getBrandId());
            skuEsModel.setBrandName(brandEntity.getName());
            skuEsModel.setBrandImg(brandEntity.getLogo());
            // 3）设置 catalogName
            skuEsModel.setCatalogName(categoryDao.selectById(skuInfoEntity.getCatalogId()).getName());
            // 4）设置 hotScore
            skuEsModel.setHotScore(0L);
            // 5）设置 attrs
            List<SkuEsModel.Attrs> attrList = searchAttrValueList.stream()
                    .filter(searchAttrValue -> searchAttrValue.getSkuId().equals(skuInfoEntity.getSkuId())) // 计算当前sku下的展示属性数据
                    .map(searchAttrValue -> {
                        SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
                        BeanUtils.copyProperties(searchAttrValue, attrs);
                        return attrs;
                    }).collect(Collectors.toList());
            skuEsModel.setAttrs(attrList);
            // 6）设置 hasStock
            if (finalStockMap == null) {
                skuEsModel.setHasStock(false);
            } else {
                Long skuId = skuInfoEntity.getSkuId();
                int stock = finalStockMap.get(skuId)==null?0 :finalStockMap.get(skuId);
                skuEsModel.setHasStock(stock > 0);
            }
            return skuEsModel;
        }).collect(Collectors.toList());

        // 3、正式存入ES
        R r1 = searchFeignService.productStatusUp(skuEsModelList);
        if(r1.getCode() != 0){
            log.info("调用远程服务存储ES失败");
            return false;
        }
        // 4、成功后将sku状态修改
        spuInfoDao.updateStatus(spuId, ProductConstant.SpuStatusEnum.UP_SPU.getCode());
        return true;
        // TODO 重复调用，接口幂等性
        // 在 feign 远程调用失败后就会执行重试机制
//        相关代码：
//        while(true){
//            try{
//                executeAndDecode(template);
//            }catch{
//                tyr{retryer.continueOrPropagate(e);}catch(){throw ex;}
//                continue;
//            }
//        }
    }

    @Override
    public SkuItemVo getProductItem(Long skuId) {
        SkuItemVo skuItemVo = new SkuItemVo();

        // 1、因为3,4,5都需要1结果的spuId，所以这里创建一个异步带返回值的请求
        CompletableFuture<SkuInfoEntity> infoFuture = CompletableFuture.supplyAsync(() -> {
            //1、sku基本信息的获取  pms_sku_info
            SkuInfoEntity skuInfoEntity = skuInfoDao.selectById(skuId);
            skuItemVo.setInfo(skuInfoEntity);
            return skuInfoEntity;
        }, threadPoolExecutor);

        // 因为后续不需要用到其结果，所以执行串行没有返回值的异步操作
        CompletableFuture<Void> salAttrFuture = infoFuture.thenAcceptAsync(res -> {
            //3、获取spu的销售属性组合
            List<SkuItemSaleAttrVo> skuItemSaleAttrVo = skuSaleAttrValueDao.getSkuItemSaleAttrVo(res.getSpuId());
            skuItemVo.setSaleAttr(skuItemSaleAttrVo);
        }, threadPoolExecutor);

        // 同上
        CompletableFuture<Void> spuDescFuture = infoFuture.thenAcceptAsync(res -> {
            //4、获取spu的介绍
            SpuInfoDescEntity spuInfoDescEntity = spuInfoDescDao.selectById(res.getSpuId());
            skuItemVo.setDesc(spuInfoDescEntity);
        }, threadPoolExecutor);

        // 同上
        CompletableFuture<Void> groupAttrsFuture = infoFuture.thenAcceptAsync(res -> {
            //5、获取spu的规格参数信息
            List<SpuItemAttrGroupVo> spuItemAttrGroupVoList = productAttrValueDao.getGroupAttrValue(res.getSpuId());
            skuItemVo.setGroupAttrs(spuItemAttrGroupVoList);
        }, threadPoolExecutor);

        //2、与上面的不同，不需要利用返回值，所以直接重新开启一个无返回值的异步请求就可以了
        CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {
            //2、sku的图片信息    pms_sku_images
            skuItemVo.setImages(skuImagesDao.selectList(new QueryWrapper<SkuImagesEntity>().eq("sku_id", skuId)));
        }, threadPoolExecutor);

        // 同上，直接开启一个异步线程
        CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {
            // 获取商品的秒杀消息
            R r = seckillFeignService.getSkuSeckillInfo(skuId);
            if (r.getCode() == 0) {
                SeckillSkuRedisTo seckillSkuRedisTo = r.getData(new TypeReference<SeckillSkuRedisTo>() {
                });
                SeckillSku seckillSku = new SeckillSku();
                BeanUtils.copyProperties(seckillSkuRedisTo, seckillSku);
                skuItemVo.setSeckillSkuVo(seckillSku);
            }
        }, threadPoolExecutor);

        // 3、启动，等待所有线程全部完成才继续执行后面代码
        try { CompletableFuture.allOf(salAttrFuture,spuDescFuture,groupAttrsFuture,imagesFuture, seckillFuture).get(); }catch(Exception e) {e.printStackTrace();}

        return skuItemVo;
    }

    @Override
    public SpuInfoTo getSpuInfoBySkuId(Long skuId) {
        Long spuId = skuInfoDao.selectById(skuId).getSpuId();
        SpuInfoEntity spuInfoEntity = spuInfoDao.selectById(spuId);

        SpuInfoTo spuInfoTo = new SpuInfoTo();
        BeanUtils.copyProperties(spuInfoEntity,spuInfoTo);

        String branName = brandDao.selectById(spuInfoEntity.getBrandId()).getName();
        spuInfoTo.setBrandName(branName);
        return spuInfoTo;
    }

}